﻿using Games.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Games.Eight
{
    class Game
    {
        public void Init(int size)
        {
            Value = Enumerable.Range(1, size * size - 1).ToList();
            Value.Add(0);
            State = string.Join(",", Value);
            this.Size = size;
            Empty = Value.IndexOf(0);
            this.Shuffle();
            this.start = string.Join(",", Value);
            this.steps = new Stack<Direction>();
        }
        public void Reload()
        {
            Value = start.Split(',').Select(x => int.Parse(x)).ToList();
            Empty = Value.IndexOf(0);
            this.steps = new Stack<Direction>();
        }

        private string start;
        public string State { get; protected set; }
        public List<int> Value { get; protected set; }
        public int Empty { get; protected set; }
        public int Size { get; protected set; }
        private Stack<Direction> steps;

        private void Shuffle()
        {
            var rd = new Random(DateTime.Now.Millisecond);
            do
            {
                for(int i = 0; i < Size * Size * 25; i++)
                {
                    var di = rd.Next(4);
                    var d = (Direction)(1 << di);
                    Move(d, null);
                }
            } while(this.IsFinished);
        }
        private bool CanMove(Direction direction)
        {
            switch(direction)
            {
                case Direction.Up:
                    return Empty < this.Size * (this.Size - 1);
                case Direction.Down:
                    return Empty > this.Size - 1;
                case Direction.Left:
                    return Empty % this.Size < this.Size - 1;
                case Direction.Right:
                    return Empty % this.Size > 0;
            }

            return false;
        }
        public async Task<bool> Move(Direction direction, Func<int, Direction, Task> callback)
        {
            if(!CanMove(direction))
            {
                return false;
            }

            int to;
            switch(direction)
            {
                case Direction.Up:
                    to = Empty + this.Size;
                    break;
                case Direction.Down:
                    to = Empty - this.Size;
                    break;
                case Direction.Left:
                    to = Empty + 1;
                    break;
                case Direction.Right:
                    to = Empty - 1;
                    break;
                default:
                    return false;
            }
            int temp = Value[to];
            Value[Empty] = temp;
            Value[to] = 0;
            Empty = to;
            if(callback != null)
            {
                this.steps.Push(direction);
                await callback(temp, direction);
            }
            return true;
        }

        public async Task Back(Func<int, Direction, Task> callback)
        {
            if(this.steps.Count == 0)
            {
                return;
            }
            Direction direction = this.steps.Pop().Reverse();

            int to;
            switch(direction)
            {
                case Direction.Up:
                    to = Empty + this.Size;
                    break;
                case Direction.Down:
                    to = Empty - this.Size;
                    break;
                case Direction.Left:
                    to = Empty + 1;
                    break;
                case Direction.Right:
                    to = Empty - 1;
                    break;
                default:
                    return;
            }
            int temp = Value[to];
            Value[Empty] = temp;
            Value[to] = 0;
            Empty = to;
            if(callback != null)
            {
                await callback(temp, direction);
            }
        }
        public bool IsFinished
        {
            get { return this.State == string.Join(",", Value); }
        }
    }
}
